#include "d3d.h"

#include "..\engine\memorymanager.h"
#include "..\engine\error.h"
#include "..\engine\engine.h"
#include "..\common\def.h"
#include "..\math\math.h"
#include "d3d_util.h"
const int D3DLight::ShaderData::size = 36 * sizeof(float); 

/**************************************
 *
 **************************************/
D3DLight::D3DLight(CD3D9Device* pDevice) : IResource(pDevice)
{
	m_State = LIGHT_ON;
}

/**************************************
 *Allows the client to externally specifiy
 *the lights world space position
 **************************************/
void D3DLight::SetPosition(const Vector3f& position)
{
	m_Position = VectorToD3DVector3(position); 
}

/**************************************
 *Allows the client to externally specifiy
 *factors of the lights lighting model
 **************************************/
void D3DLight::SetLightCoefficents(const float ambient, const float diffuse, const float specular)
{
	m_ShaderData.diffuseColor[3] = diffuse;
	m_ShaderData.ambientColor[3] = ambient;
	m_ShaderData.aux[3] = specular;
}

/**************************************
 *Allows the client to externally specifiy
 *the lights attenuation
 **************************************/
void D3DLight::SetAttenuation(const float intensity)
{
	Attenuation = intensity; 
}

/**************************************
 *Allows the client to externally specifiy
 *the lights diffuse color
 **************************************/
void D3DLight::SetDiffuseColor(const Color3f &color)
{
	DiffuseColor=color; 
}

/**************************************
 *Allows the client to externally specifiy
 *the lights ambient color
 **************************************/
void D3DLight::SetAmbientColor(const Color3f &color)
{
	AmbientColor=color; 
}

/**************************************
 *No implementation required
 **************************************/
HRESULT D3DLight::Reload()
{
	return S_OK; 
}

/**************************************
 *
 **************************************/
string D3DLight::GetType() const
{
	return string("D3DLight"); 
}

/**************************************
 *Allows the client to externally specifiy
 *the lights on/off state
 **************************************/
void D3DLight::SetState(const LightState state)
{
	m_State = state;
}

/**************************************
 *Allows the client to externally determine
 *the lights on/off state
 **************************************/
D3DLight::LightState D3DLight::GetState()
{
	return m_State;
}

/**************************************
 *Releases all resources associated with
 *this instance and restores it's origonal
 *state
 **************************************/
HRESULT D3DLight::Release()
{
	m_State = LIGHT_OFF;
	m_IsValid = false; 
	
	if(FAILED(GetDevice()->DeregisterResource(this)))
		m_IsValid = true;

	return S_OK; 
}

/**************************************
 *Data packet used internally during 
 *rendering
 **************************************/
D3DLight::ShaderData* D3DLight::GetShaderData()
{ 
	m_ShaderData.position[0] = m_Position.x; 
	m_ShaderData.position[1] = m_Position.y; 
	m_ShaderData.position[2] = m_Position.z; 

	if(m_State == LIGHT_OFF)
	{
		m_ShaderData.diffuseColor[0] =  0; 
		m_ShaderData.diffuseColor[1] = 0; 
		m_ShaderData.diffuseColor[2] = 0;  

		m_ShaderData.ambientColor[0] = 0; 
		m_ShaderData.ambientColor[1] = 0; 
		m_ShaderData.ambientColor[2] = 0; 
	}
	else
	{
		m_ShaderData.diffuseColor[0] =  DiffuseColor.r; 
		m_ShaderData.diffuseColor[1] = DiffuseColor.g; 
		m_ShaderData.diffuseColor[2] = DiffuseColor.b; 

		m_ShaderData.ambientColor[0] = AmbientColor.r; 
		m_ShaderData.ambientColor[1] = AmbientColor.g; 
		m_ShaderData.ambientColor[2] = AmbientColor.b; 
	}

	m_ShaderData.aux[0] = Attenuation;  

	return &m_ShaderData; 
}

/******************************************************************************************************************
 *
 ******************************************************************************************************************/

/**************************************
 *
 **************************************/
D3DDirectionalLight::D3DDirectionalLight(CD3D9Device* const pDevice) : D3DLight(pDevice)
{
	m_Range = 1.0f;
	mDepthBuffer = NULL;
	m_Direction = VectorToD3DVector3(Vector3f::FORWARD); 
	mb_Shadow = false; 
	m_UpdatePending = false;
	
	CCamera* const pCamera = CEngine::GetInstance()->GetCamera();
	D3DXMatrixOrthoLH(&m_ViewOrrientation.m_Projection, 256,256, pCamera->getNearPlane(),pCamera->getFarPlane());
	EE_ZEROMEMORY(&m_ShaderData,ShaderData::size); 
}

/**************************************
 *
 **************************************/
D3DDirectionalLight::~D3DDirectionalLight()
{
	mDepthBuffer->Release(); 
	EE_SAFEDELETE(mDepthBuffer); 
}

/**************************************
 *Data packet used internally during 
 *rendering
 **************************************/
D3DLight::ShaderData* D3DDirectionalLight::GetShaderData()
{
	if(m_State == LIGHT_OFF)
	{
		m_ShaderData.diffuseColor[0] =  0; 
		m_ShaderData.diffuseColor[1] = 0; 
		m_ShaderData.diffuseColor[2] = 0;  

		m_ShaderData.ambientColor[0] = 0; 
		m_ShaderData.ambientColor[1] = 0; 
		m_ShaderData.ambientColor[2] = 0; 
	}
	else
	{
		m_ShaderData.diffuseColor[0] =  DiffuseColor.r; 
		m_ShaderData.diffuseColor[1] = DiffuseColor.g; 
		m_ShaderData.diffuseColor[2] = DiffuseColor.b; 

		m_ShaderData.ambientColor[0] = AmbientColor.r; 
		m_ShaderData.ambientColor[1] = AmbientColor.g; 
		m_ShaderData.ambientColor[2] = AmbientColor.b; 
	}
	
	m_ShaderData.position[0] = m_Position.x; 
	m_ShaderData.position[1] = m_Position.y; 
	m_ShaderData.position[2] = m_Position.z;

	D3DXVec3Normalize(&m_Direction,&m_Direction);
	m_ShaderData.direction[0] = m_Direction.x; 
	m_ShaderData.direction[1] = m_Direction.y; 
	m_ShaderData.direction[2] = m_Direction.z; 

	m_ShaderData.aux[0] = Attenuation; 
	m_ShaderData.aux[1] = m_Range;
	m_ShaderData.aux[2] = 0; 
	

	return &m_ShaderData; 
}

/**************************************
 *Allows the client to access the light's
 *view and projection transforms
 **************************************/
ViewOrrientation* D3DDirectionalLight::GetViewOrrientation()
{
	return &m_ViewOrrientation;
}

/**************************************
 *Allows the client to determine if the
 *light casts a shadow
 **************************************/
bool D3DDirectionalLight::IsShadowed()
{
	return mb_Shadow; 
}

/**************************************
 *
 **************************************/
string D3DDirectionalLight::GetType() const
{
	return string("D3DDirectionalLight"); 
}

/**************************************
 *Allows the client to externally specify
 *the light's range of extent
 **************************************/
void D3DDirectionalLight::SetRange(const float range) {
	m_Range = range;
}

/**************************************
 *Allows the client to externally specify
 *the light's direction
 **************************************/
void D3DDirectionalLight::SetDirection(const Vector3f& direction)
{
	if(Vector3f::isZero(&direction) == false)
	{	
		m_Direction =  VectorToD3DVector3(direction); 
		m_UpdatePending = true;
	}
}

/**************************************
 *Allows the client to externally specify
 *the light's position in world space
 **************************************/
void D3DDirectionalLight::SetPosition(const Vector3f& position)
{
	m_Position = VectorToD3DVector3(position);
	m_UpdatePending = true;
}

/**************************************
 *Allows the client to externally specify
 *if the light casts shadows
 **************************************/
void D3DDirectionalLight::Update()
{
	if(mb_Shadow && m_UpdatePending)
	{
		const D3DXVECTOR3 lookat = m_Position + m_Direction;
		const D3DXVECTOR3 up = (m_Direction.x != 0.0f || m_Direction.z != 0.0f) ? D3DXVECTOR3(0,1,0) : D3DXVECTOR3(1,0,0);

		D3DXMatrixLookAtLH(&m_ViewOrrientation.m_View, &m_Position, &lookat, &up);
		D3DXMATRIX vp;
		D3DXMatrixMultiply(&vp,&m_ViewOrrientation.m_View ,&m_ViewOrrientation.m_Projection);
		memcpy(m_ShaderData.lightViewProj,&vp, sizeof(float) * 16); 

		m_UpdatePending = false;
	}

	GetDevice()->RenderDepthMap(mDepthBuffer, &m_ViewOrrientation); 
}

/**************************************
 *Reloads resources associated with this
 *instace during runtime
 **************************************/
HRESULT D3DDirectionalLight::Reload()
{
	if(mDepthBuffer)
	{
		mDepthBuffer->Release();
		GetDevice()->createRenderTarget(mDepthBuffer, 512, 512, D3DFMT_G16R16F, false);
	}
	return S_OK; 
}

/**************************************
 *Releases all resources associated with
 *this instance and restores it's origonal
 *state
 **************************************/
HRESULT D3DDirectionalLight::Release()
{
	m_Direction = VectorToD3DVector3(Vector3f::FORWARD);
	m_UpdatePending = false;
	mb_Shadow  = false; 

	if(mDepthBuffer)
	{
		mDepthBuffer->Release(); 
		EE_SAFEDELETE(mDepthBuffer);
	}
	
	m_IsValid = false; 
	if(FAILED(GetDevice()->DeregisterResource(this)))
		m_IsValid = true;

	return S_OK; 
}

/**************************************
 *Allows the client to externally specify
 *if the light casts shadows
 **************************************/
HRESULT D3DDirectionalLight::SetShadow(const bool hasShadow)
{
	if(hasShadow)
	{
		mb_Shadow = true; 
		if(mDepthBuffer)
		{
			if(FAILED(mDepthBuffer->Release()))
				return E_FAIL;
		}
		else
			mDepthBuffer = EE_NEW RenderTarget(GetDevice()); 

		if(FAILED(GetDevice()->createRenderTarget(mDepthBuffer, 512, 512, D3DFMT_G16R16F, false)))
			return E_FAIL;
	}
	else
	{
		mb_Shadow = false;
		if(mDepthBuffer)
		{
			if(FAILED(mDepthBuffer->Release()))
				return E_FAIL;
			EE_SAFEDELETE(mDepthBuffer);
		}
	}
	return S_OK; 
}

/**************************************
 *Allows the client to externally create
 *an omni light instance
 **************************************/
HRESULT CD3D9Device::CreateLight(const Vector3f& position, const Color3f& diffuseColor, const Color3f& ambientColor, float range,D3DLight** pOut)
{
	if(ISNULL(pOut))
		return E_FAIL; 

	D3DLight* pLight = EE_NEW D3DLight(this); 

	pLight->SetDiffuseColor(diffuseColor); 
	pLight->SetAmbientColor(ambientColor); 
	pLight->SetAttenuation(1.0f); 
	pLight->SetPosition(position); 
	pLight->SetLightCoefficents(1.0f,1.0f,1.0f);
	m_Lights.Add(pLight); 

	*pOut = pLight; 
	return S_OK; 
}

/**************************************
 *Allows the client to externally create
 *a directional light instance
 **************************************/
HRESULT CD3D9Device::CreateDirectionalLight(const Vector3f& position, const Vector3f& direction, const Color3f& diffuseColor, const Color3f& ambientColor, float range,D3DDirectionalLight **pOut)
{
	if(ISNULL(pOut))
		return E_FAIL; 

	D3DDirectionalLight* pLight = EE_NEW D3DDirectionalLight(this); 

	pLight->SetDiffuseColor(diffuseColor); 
	pLight->SetAmbientColor(ambientColor); 
	pLight->SetAttenuation(1.0f); 
	pLight->SetPosition(position); 
	pLight->SetDirection(direction); 

	m_DirectionalLights.Add(pLight); 

	*pOut = pLight; 
	return S_OK; 
}

/**************************************
 *Allows the client to externally create
 *a directional light instance
 **************************************/
HRESULT CD3D9Device::CreateDirectionalLight(D3DDirectionalLight **pOut)
{
	return CreateDirectionalLight(Vector3f(),Vector3f(0,0,-1), Color3f::COLOR_WHITE, Color3f::COLOR_BLACK, 1.0f, pOut); 
}

/**************************************
 *Allows the client to externally create
 *an omni light instance
 **************************************/
HRESULT CD3D9Device::CreateLight(D3DLight** pOut)
{
	return CreateLight(Vector3f(), Color3f::COLOR_WHITE, Color3f::COLOR_BLACK,1.0f, pOut); 
}